//
//	MiscClipTextFieldCell.h -- a cell for displaying long string values
//		Written and Copyright (c) 1995 by Balazs Pataki. 
//				Version 1.0.  All rights reserved.
//
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

#import <appkit/appkit.h>
#import	<misckit/MiscString.h>
#import <objc/objc-runtime.h>
#import "MiscClipTextFieldCell.h"


#define	CLASS_NAME				"MiscClipTextFieldCell"
#define	CLASS_VERSION			1


#define	DEFAULTCLIPPER			"..."
#define DELIMITERS				[delimiters stringValue]


// To make texts displayed in cells looking good inside views, the maximum 
// width of a cell is 4.0 points (o whatever) less than the width of the view
// the cell is displayed in. So, we need this little adjustment (am I right? 
// - maybe not but at least it works) 
#define CELL_ADJUSTMENT			4.0
#define BEZELED_ADJUSTMENT		5.0
#define	BORDERED_ADJUSTMENT		4.0


#define FLUSH	[[[control window] reenableFlushWindow] flushWindow];


@interface MiscClipTextFieldCell (Private)
- _clipStringValue:(const char *)aString;
- _copyClipperObj:obj fromZone:(NXZone *)zone;
- _copyDelimitersObj:obj fromZone:(NXZone *)zone;
- _copyFullStringObj:obj fromZone:(NXZone *)zone;
@end


/*
						********************************
						*                              *
						*    MiscClipTextFieldCell     *
						*                              *
						********************************
*/

@implementation MiscClipTextFieldCell

+ initialize
// Set class version
{
	if (self == objc_lookUpClass(CLASS_NAME))  {
		[self setVersion:CLASS_VERSION];
	}
	return self;
}

- init;
// Initializes a newly allocated MiscClipTextFieldCell with default values, 
// that is the clipper string to "...", uses no delimiters when clipping, and
// clipping happens on the right. The cell's other attributes are set for
// displaying text only (no scroll, selection allowed).  
{
	return [self  initTextCell:""];	
}

- initTextCell:(const char*)aString;
{
	[super initTextCell:aString];

	clipper = [[MiscString allocFromZone:[self zone]] 
						   initString:DEFAULTCLIPPER];
	fullString = [[MiscString allocFromZone:[self zone]] init];
	delimiters  = nil;				/* As a deafult we don't have delimiters*/
	clipOnRight = YES;				/*	and clipping happens on the right	*/
	clipEnabled = YES;
	
	[self setDelegate:self];
	// MiscClipField is initialized for only displaying its string value 
	[self setBezeled:YES];
	[self setEditable:NO];
	[self setScrollable:NO];
	[self setBackgroundGray:NX_LTGRAY];

	return self;
}

- copyFromZone:(NXZone *)zone
{
	id obj = [super copyFromZone:zone];	
	
	[obj _copyClipperObj:clipper fromZone:zone];
	[obj _copyDelimitersObj:delimiters fromZone:zone];
	[obj _copyFullStringObj:fullString fromZone:(NXZone *)zone];
	
	return obj;
}

- free
{
	[fullString free];
	[clipper free];
	if (delimiters)
		[delimiters free];
	
	return [super free];
}


- setDelegate:anObject
{
	delegate = anObject;
	return self;
}



- setClipOnRight:(BOOL) flag
// If flag is YES clipping happens on the right, otherwise on the left of the 
// string in the cell
{
	clipOnRight = flag;

	return self;
}


- setClipperString:(const char*) aString
// Sets  `aString' as the string that is displayed in place of the clipped part
// of the original string
{
	if (clipper)
		[clipper setStringValue:aString];
	else
		clipper = [MiscString newWithString:aString];

	return self;
}


- setClipDelimiters:(const char*) delimChars
// Sets  `delimChars' as delimiters by which the clipping has to happen
{
	if (delimiters) 
		[delimiters setStringValue:delimChars];
	else
		delimiters = [MiscString newWithString:delimChars];

	return self;
}

- setClipEnabled:(BOOL) flag
// Sets whether the next `setStringValue:' message should clip the text or not 
{
	clipEnabled = flag;
	return self;
}


- setStringValue:(const char *)aString
{
	if ([fullString cmp:aString] != 0)
		[fullString setStringValue:aString];

	if ( clipEnabled )
		return [self _clipStringValue:aString];
	
	return [super setStringValue:aString];
	
}

- takeStringValueFrom:sender
{
	[self setStringValue:[sender stringValue]];
	return self;
}

- resetStringValue:sender
{
	if ( clipEnabled )
		return [self _clipStringValue:[self fullStringValue]];
	
	return [super setStringValue:[self fullStringValue]];
}


- (const char*) fullStringValue { return [fullString stringValue]; }
- (BOOL) isClipEnabled			{ return clipEnabled; }
- (BOOL) doesClipOnRight		{ return clipOnRight; }
- clipper		{ return clipper;	 }
- delimiters	{ return delimiters; }	
- delegate		{ return delegate;	 }

- (BOOL) isWrapped
// Returns yes if the Cell wraps the text by word
{
	return ( cFlags2.noWrap ? NO : YES );
}


- write:(NXTypedStream *)stream
{
    [super write:stream];
    
    NXWriteObject(stream, fullString);
    NXWriteObject(stream, clipper);
    NXWriteObject(stream, delimiters);
    NXWriteObject(stream, delegate);
    NXWriteType(stream, @encode(BOOL), &clipOnRight);
    NXWriteType(stream, @encode(BOOL), &clipEnabled);
   
    return self;
}


- read:(NXTypedStream *)stream
{
    [super read:stream];
   
	fullString = NXReadObject(stream);
	clipper    = NXReadObject(stream);
	delimiters = NXReadObject(stream);
	delegate   = NXReadObject(stream);
	NXReadType(stream, @encode(BOOL), &clipOnRight);
	NXReadType(stream, @encode(BOOL), &clipEnabled);

	return self;
}


- awake
// Does nothing but with speed :-)
{
	return self;
}



@end


/*
						********************************
						*                              *
						*   MiscClipTextField(Private) *
						*                              *
						*    -- Private Methods --     *
						*                              *
						********************************
*/


@implementation MiscClipTextFieldCell (Private)

- _clipStringValue:(const char *)aString
// A very long story...
// 
{
	id	control	 = 	nil;
	id	temp 	 = 	nil;
	id 	mainFont = 	[self font];
	id 	scrnFont = 	[mainFont screenFont];							
	id 	theFont  = 	(scrnFont ? scrnFont : mainFont); // try to use screen font
	float 	clipperWidth =	0; 
	float 	clippedWidth =	0;
	float 	cellWidth	 =  0;
	BOOL	doClip		 = NO;
	BOOL  	usingDelimiters = NO;
	NXRect	biggestRect;
	NXSize	size;
	int		i=0;
	
	if (!aString) 
		return self;
	
											/* More initializing			*/		
	if (![clipper stringValue])
		[clipper setStringValue:DEFAULTCLIPPER];

	clipperWidth =	[theFont getWidthOf:[clipper stringValue]]; 	

	temp = [[MiscString alloc] initString:aString];

	usingDelimiters = ( delimiters && ![delimiters emptyString] ? YES : NO );
	
	// Figure out the initial cell width (the biggest possible cell width)
	// [and do some necessary adjustment too]
	control = [self controlView];
										/* Matrix will respond ...			*/	 
	if ( [control respondsTo:@selector(getCellSize:)] ) {
		NXSize	size;
		[control getCellSize:&size];
		cellWidth = size.width;
		biggestRect.size.width = size.width;
		biggestRect.size.height = size.height;
	}
	else {
		[control getFrame:&biggestRect];
		cellWidth =	NX_WIDTH(&biggestRect);
	}
	
	cellWidth -= CELL_ADJUSTMENT;
	
	if ([self isBezeled])
		cellWidth -= BEZELED_ADJUSTMENT;
	else if ([self isBordered])
		cellWidth -= BORDERED_ADJUSTMENT;
	
	// Configuring the cell
	[self setSelectable:NO];		/* Disable selecting and scrolling		*/
	[self setScrollable:NO];

	[[control window] disableFlushWindow];

	// Check whether we need to clip or not. If we do give delegate an
	// opportunity to clip the string in his custom way. If `temp's string
	// value is still long we do the real clipping. I know its not really
	// sexy doing this with a for loop but ...
	for (i=1; i<=2; i++) {
		doClip = NO;
		
		if ([theFont getWidthOf:[temp stringValue]] > cellWidth)	
		  	doClip = YES;			/* aString is too wide so do the clip	*/
		else {						/* Try the "trick" 						*/
									/* Set the clipped string value 		*/
			[super setStringValue:[temp stringValue]];	
									/* Calc how much space it needed to be	*/
									/*  displayed							*/
			[self calcCellSize:&size inRect:&biggestRect];
									/* It didn't fit so its broken into many*/
									/* lines (ie: the cell got higher) 		*/
			if ((size.height > NX_HEIGHT(&biggestRect)))
				doClip = YES;		/* Do the clip							*/
		}
		if (i==1 && doClip)
			[delegate stringWillBeClipped:temp];
	}
	
	
	// Clip on the right if `aString' is too long
	if ( doClip && clipOnRight ) {
		BOOL cutMore=NO;	// This becomes true if text doesn't fits into 
							// the cell in the first round 
						/* The loop cuts characters from the end and checks	*/
						/*	wheter it's short enough to be displayed		*/
		while (YES) {			
			clippedWidth=[theFont getWidthOf:[temp stringValue]];
								/* If the string is still too long			*/
			if ( ((clippedWidth+clipperWidth) >= cellWidth) || cutMore)  {
				[temp removeFrom:[temp length]-1 length:1];
				if ( usingDelimiters ) {
								/* No delimiter found means we reached the 	*/
								/*	last delimited string element and it 	*/
								/*  can't be shortened anymore because there*/
								/*	is no "next" delimter backwards			*/
					int from = [temp rspotOfChars:DELIMITERS];
					if ( from == -1 )
						[temp replace:[temp stringValue] with:""];
					else
						[temp removeFrom:from+1 length:[temp length]-1];
				}
								/* Last element reached and still long		*/
				if ([temp emptyString]) {
					[temp setStringValue:[clipper stringValue]];
					FLUSH;
					return [super setStringValue:[temp stringValueAndFree]];
				}
				cutMore = NO;
			} //End if (still too long)
			else {
				[temp concatenate:clipper];
									/* Set the clipped string value 		*/
				[super setStringValue:[temp stringValue]];	
									/* Calc how much space it needed to be	*/
									/*  displayed							*/
				[self calcCellSize:&size inRect:&biggestRect];
									/* It didn't fit so its broken into two */
									/* lines (ie: the cell got higher) 		*/
				if ( size.height > NX_HEIGHT(&biggestRect) ){
					cutMore = YES;	/* Force cutting some more				*/
									/* Remove clipper string from end		*/
					[temp removeFrom:([temp length]-[clipper length]) 
						  length:[clipper length]];
				}			
				else {			/* Finished, string value can be displyed	*/
					[temp free];
					FLUSH;
					return self;
				}
			} // End else (check if string value can really be displayed)
		} // End of string shortening loop
	}//End if (clipOnRight)



	// Clip on the left if `aString' is too long
	// (This code is nearly identical to the right clipping one, except that
	//  it scans the string from left to right. Can you see any way to merge 
	//  these two in a meaningful way?) 
	if ( doClip && !clipOnRight ) {
		BOOL cutMore=NO;	// This becomes true if text doesn't fits into 
							// the cell in the first round 		
						/* The loop cuts characters from the beg. of temp	*/
						/*	and checks	wheter it's short enough to be 		*/
						/*	displayed										*/
		while ( YES ) {
						// This sometimes lies, maybe because of the screenfont
						// - printerfont differency
			clippedWidth=[theFont getWidthOf:[temp stringValue]];
								/* If string is still too long	or we are	*/
								/*	forced to cut more ...					*/
			if ( ((clippedWidth+clipperWidth) >= cellWidth) || cutMore)  {
				[temp removeFrom:0 length:1];
				if ( usingDelimiters ) {
					int to=[temp spotOfChars:DELIMITERS];
								/* No delimiters found means we reached the */
								/*	last delimited string element and it 	*/
								/*  can't be shortened anymore because there*/
								/*	is no "next" delimter forward			*/
					if (to == -1)
						[temp replace:[temp stringValue] with:""];
					else
						[temp removeFrom:0 to:to-1];
				}
								/* Last element reached and still long		*/
				if ([temp emptyString]) {
					[temp setStringValue:[clipper stringValue]];
					FLUSH;
					return [super setStringValue:[temp stringValueAndFree]];
				}
				cutMore = NO;	/* Turn forcing cutting more off			*/
			} //Enf if (still too long)
			else {	
									/* Insert clipper						*/
				[temp insertString:clipper];
									/* Set the clipped string value 		*/
				[super setStringValue:[temp stringValue]];	
									/* Calc how much space it needed to be	*/
									/*  displayed							*/
				[self calcCellSize:&size inRect:&biggestRect];
				
				if ( size.height > NX_HEIGHT(&biggestRect) ){
									/* It didn't fit so its broken into many*/
									/* lines (ie: the cell got higher) 		*/
					cutMore = YES;	/* Force cutting some more				*/
									/* Remove clipper string from front		*/
					[temp removeFrom:0 length:[clipper length]];
				}			
				else {			/* Finished, string value can be displyed*/
					[temp free];
					FLUSH;
					return self;
				}
			} // End else (check if string value can really be displayed)
		} // End of string shortening loop
	}//End if (clip on left)

	FLUSH;
	return [super setStringValue:[temp stringValueAndFree]];
	
}

- _copyClipperObj:obj fromZone:(NXZone *)zone
{
	id newClipper = [obj copyFromZone:zone];

	clipper = newClipper;

	return self;
}

- _copyDelimitersObj:obj fromZone:(NXZone *)zone
{
	id newDelim = [obj copyFromZone:zone];

	delimiters = newDelim;

	return self;
}

- _copyFullStringObj:obj fromZone:(NXZone *)zone
{
	id newString = [obj copyFromZone:zone];

	fullString = newString;

	return self;
}

@end



@implementation Object (MiscClipDelegate)
- stringWillBeClipped:theString		{ return self; }
@end


@implementation MiscClipTextFieldCell(IBStuff)
- (const char *)getInspectorClassName
// Return the class name of our inspector.
{
	return "MiscClipTextFieldInspector";
}
@end


